﻿using CommonUtils;
using MudEngine.Parsing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MudEngine.Runtime
{
    /// <summary>
    /// Forward-only linked list of RuntimeContext objects
    /// </summary>
    public class RuntimeContext
    {
        /// <summary>
        /// points to the next RuntimeContext in the linked list
        /// </summary>
        public RuntimeContext Next { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public ErrorContext CurrentErrorContext { get; set; }
        /// <summary>
        /// Runtime stack
        /// </summary>
        private Value[] _stack;
        /// <summary>
        /// Top of stack
        /// </summary>
        public int SP { get; set; }
        /// <summary>
        /// Top of control stack
        /// </summary>
        public int CSP { get; set; }
        /// <summary>
        /// Control stack
        /// </summary>
        public ControlStackContext[] ControlStack { get; private set; }
        /// <summary>
        /// caller of the last command
        /// </summary>
        public object_t Caller { get; set; }
        /// <summary>
        /// Caller stack
        /// </summary>
        public Stack<object_t> CallerStack { get; private set; }
        /// <summary>
        /// Holds the currently running object
        /// </summary>
        public object_t CurrentObject { get; set; }
        /// <summary>
        /// Holds a link to the previous object
        /// </summary>
        public object_t PreviousObject { get; set; }
        /// <summary>
        /// The framepointer to manage the parameters and return pointer for function calls
        /// </summary>
        public int FramePointer { get; set; }
        /// <summary>
        /// The current source being interpreted
        /// </summary>
        public Program CurrentProgram { get; set; }
        /// <summary>
        /// the pointer to the position in the source
        /// </summary>
        public int ProgramCounter { get; set; }
        /// <summary>
        /// offset of the function index (inheritance)
        /// </summary>
        public int FunctionIndexOffset { get; set; }
        /// <summary>
        /// offset of the variable index (inheritance)
        /// </summary>
        public int VariableIndexOffset { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public RuntimeContext()
        {
            this._stack = new Value[MudContext.INT_STACK_SIZE];
            this.ControlStack = new ControlStackContext[MudContext.INT_CONTROLSTACK_SIZE];
            this.CallerStack = new Stack<object_t>();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="frkind"></param>
        public void PushControlStack(FrameKind frkind)
        {
            if (CSP + 1 >= this.ControlStack.Length)
                throw new RuntimeException("Stack overflow: Too deep recursion");

            CSP++;
            if (this.ControlStack[CSP] == null)
                this.ControlStack[CSP] = new ControlStackContext();
            this.ControlStack[CSP].Ob = this.CurrentObject;
            this.ControlStack[CSP].PreviousOb = this.PreviousObject;
            this.ControlStack[CSP].FramePointer = this.FramePointer;
            this.ControlStack[CSP].CurrentProgram = this.CurrentProgram;
            this.ControlStack[CSP].ProgramCounter = this.ProgramCounter;
            this.ControlStack[CSP].FrameKind = frkind;
            this.ControlStack[CSP].VariableIndexOffset = this.VariableIndexOffset;
            this.ControlStack[CSP].FunctionIndexOffset = this.FunctionIndexOffset;
        }
        /// <summary>
        /// 
        /// </summary>
        public void PopControlStack()
        {
            if (CSP - 1 < 0)
                throw new RuntimeException("Stack underflow");

            this.CurrentObject = this.ControlStack[CSP].Ob;
            this.CurrentProgram = this.ControlStack[CSP].CurrentProgram;
            this.PreviousObject = this.ControlStack[CSP].PreviousOb;
            this.ProgramCounter = this.ControlStack[CSP].ProgramCounter;
            this.FramePointer = this.ControlStack[CSP].FramePointer;
            this.VariableIndexOffset = this.ControlStack[CSP].VariableIndexOffset;
            this.FunctionIndexOffset = this.ControlStack[CSP].FunctionIndexOffset;

            this.ControlStack[CSP].Ob = null;
            this.ControlStack[CSP].CurrentProgram = null;
            this.ControlStack[CSP].PreviousOb = null;

            CSP--;
        }
        /// <summary>
        /// 
        /// </summary>
        public ControlStackContext TopOfControlStack
        {
            get
            {
                return this.ControlStack[CSP];
            }
        }

        public void SaveContext(ErrorContext errorContext)
        {
            errorContext.SavedSP = this.SP;
            errorContext.SavedCSP = this.CSP;
            errorContext.SavedCP = this.CallerStack.Count;
            errorContext.Prev = CurrentErrorContext;

            CurrentErrorContext = errorContext;
        }

        public void RestoreContext(ErrorContext errorContext)
        {
            while (this.CallerStack.Count > errorContext.SavedCP)
                this.RestoreCaller();

            Assert.IsTrue(this.CSP >= errorContext.SavedCSP, "csp is below econ->csp before unwinding.");
            if (this.CSP > errorContext.SavedCSP)
            {
                // must pop last ControlStack entry to restore to the right control stack position. So add 1
                this.CSP = errorContext.SavedCSP + 1;
                this.PopControlStack();
            }
            this.Pop_N_Elements(this.SP - errorContext.SavedSP);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="errorContext"></param>
        public void PopContext(ErrorContext errorContext)
        {
            CurrentErrorContext = errorContext.Prev;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="functionIdx"></param>
        /// <returns></returns>
        public Function SetupNewFrame(int functionIdx)
        {
            this.FunctionIndexOffset = 0;
            this.VariableIndexOffset = 0;

            int mid, high, low;
            while (this.CurrentProgram.FunctionTable[functionIdx].Flags.HasFlag(FunctionFlags.Inherited))
            {
                low = 0;
                high = this.CurrentProgram.Inherits.Count - 1;
                while (high > low)
                {
                    mid = (low + high + 1) >> 1;
                    if (this.CurrentProgram.Inherits[mid].FunctionIndexOffset > functionIdx)
                        high = mid - 1;
                    else
                        low = mid;
                }

                functionIdx -= this.CurrentProgram.Inherits[low].FunctionIndexOffset;
                this.FunctionIndexOffset += this.CurrentProgram.Inherits[low].FunctionIndexOffset;
                this.VariableIndexOffset += this.CurrentProgram.Inherits[low].VariableIndexOffset;
                this.CurrentProgram = this.CurrentProgram.Inherits[low].Program;
            }

            Function func = this.CurrentProgram.FunctionTable[functionIdx];

            this.SetupVariables(this.TopOfControlStack.NumLocalVariables, func.NumArguments, func.NumLocals);

            return func;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="functionIdx"></param>
        /// <returns></returns>
        public Function SetupInheritedFrame(int functionIdx)
        {
            int mid, high, low;
            while (this.CurrentProgram.FunctionTable[functionIdx].Flags.HasFlag(FunctionFlags.Inherited))
            {
                low = 0;
                high = this.CurrentProgram.Inherits.Count - 1;
                while (high > low)
                {
                    mid = (low + high + 1) >> 1;
                    if (this.CurrentProgram.Inherits[mid].FunctionIndexOffset > functionIdx)
                        high = mid - 1;
                    else
                        low = mid;
                }

                functionIdx -= this.CurrentProgram.Inherits[low].FunctionIndexOffset;
                this.FunctionIndexOffset += this.CurrentProgram.Inherits[low].FunctionIndexOffset;
                this.VariableIndexOffset += this.CurrentProgram.Inherits[low].VariableIndexOffset;
                this.CurrentProgram = this.CurrentProgram.Inherits[low].Program;
            }

            Function func = this.CurrentProgram.FunctionTable[functionIdx];

            this.SetupVariables(this.TopOfControlStack.NumLocalVariables, func.NumArguments, func.NumLocals);

            return func;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="actualArgumentsPushed"></param>
        /// <param name="numArguments"></param>
        /// <param name="numLocals"></param>
        public void SetupVariables(int actualArgumentsPushed, int numArguments, int numLocals)
        {
            int tmp = actualArgumentsPushed - numArguments;
            if (tmp > 0)
            {
                this.Pop_N_Elements(tmp);
                this.PushUndefines(numLocals);
            }
            else
            {
                this.PushUndefines(numLocals - tmp);
            }
            this.TopOfControlStack.NumLocalVariables = numLocals + numArguments;
            this.FramePointer = this.SP - this.TopOfControlStack.NumLocalVariables + 1;
        }

        #region Stack operations
        /// <summary>
        /// 
        /// </summary>
        /// <param name="val"></param>
        public void Push(Value val)
        {
            if (this.SP + 1 >= _stack.Length)
                throw new RuntimeException("Pushing value '{0}' will cause a stack overflow", val);

            this._stack[++this.SP] = val;
        }
        /// <summary>
        /// 
        /// </summary>
        public void PushOK()
        {
            this.Push(Value.constI1_1);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="count"></param>
        public void PushUndefines(int count)
        {
            while (count-- > 0)
                this.Push(Value.constI1_0);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="count"></param>
        public void PushUndefined()
        {
            this.Push(Value.constI1_0);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Value Pop()
        {
            if (this.SP < 0)
                throw new RuntimeException("Stack is empty");

            Value retVal = this._stack[this.SP];

            FreeValueFromStack(this.SP--);
            //this.FireStackChangedEvent();

            //this.RenderStack();
            return retVal;
        }

        private void FreeValueFromStack(int sp)
        {
            this._stack[sp] = null;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public void Pop_N_Elements(int elements)
        {
            if (SP - elements < 0)
                throw new RuntimeException("There are not that many elements on the stack");

            for (int i = 0; i < elements; i++)
                FreeValueFromStack(this.SP - i);
            this.SP -= elements;
            //this.FireStackChangedEvent();

            //this.RenderStack();
        }
        /// <summary>
        /// 
        /// </summary>
        public void Pop_2_Elements()
        {
            this.Pop_N_Elements(2);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Value Peek()
        {
            if (SP < 0)
                throw new RuntimeException("Stack is empty");

            return _stack[SP];
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="idx"></param>
        /// <returns></returns>
        public Value GetStackValue(int idx)
        {
            if (idx < 0 || idx > SP)
                throw new RuntimeException("Stack out of bounds.");

            return _stack[idx];
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="idx"></param>
        /// <param name="val"></param>
        public void SetStackValue(int idx, Value val)
        {
            if (idx < 0 || idx > SP)
                throw new RuntimeException("Stack out of bounds.");

            this._stack[idx] = val;
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        public void SetCaller(object_t obj)
        {
            this.Caller = obj;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        public void SaveCaller(object_t obj)
        {
            this.CallerStack.Push(this.Caller);
            this.SetCaller(obj);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public object_t RestoreCaller()
        {
            if (this.CallerStack.Count == 0)
                throw new RuntimeException("Caller stack underflow");

            object_t obj = this.CallerStack.Pop();
            this.SetCaller(obj);
            return obj;
        }

        #region Program load routines
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public byte Load1()
        {
            return CurrentProgram.Load1(ProgramCounter++);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public short Load2()
        {
            short v = CurrentProgram.Load2(ProgramCounter);
            ProgramCounter += 2;
            return v;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public int Load4()
        {
            int v = CurrentProgram.Load4(ProgramCounter);
            ProgramCounter += 4;
            return v;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public long Load8()
        {
            long v = CurrentProgram.Load8(ProgramCounter);
            ProgramCounter += 4;
            return v;
        }
        #endregion
    }

    public class ErrorContext
    {
        public int SavedSP { get; set; }

        public int SavedCSP { get; set; }

        public int SavedCP { get; set; }

        public ErrorContext Prev { get; set; }
    }
}
